#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <pthread.h>

#define __USE_GNU // for accept4 in sys/socket.h

#include "global_var.h"
#include "jthreads.h"
#include "socket.h"
#include "accept.h"
#include "handle.h"

/**
 * @brief 
 * 
 * @param income_fd accepted fd
 * @param income_addr sockaddr_storage
 * @return income_t* remember to free!
 */
income_t *gen_income(int income_fd, struct sockaddr_storage *income_addr)
{
	income_t *in;
	in = (income_t *)malloc(sizeof(income_t));
	memset(in, 0, sizeof(income_t));
	in->fd = income_fd;
	in->addr = *income_addr; // copy
	in->accept_time = time(NULL);
	in->ans_code = a220;
	in->recv_DATA_flag = 0;
	in->mail_store = NULL;

	return in; // remember to free
}

int *accept_start(accept_thread_t *thread)
{
	struct epoll_event events[10];
	while (1)
	{
		int ewn = epoll_wait(thread->socket_epfd, events, 10, 2000);
		if (ewn == -1)
		{
			fprintf(stderr, "epoll_wait return -1, %d\n", errno);
		}
		else
		{
			for (int i = 0; i < ewn; i++)
			{
				if (events[i].events & EPOLLIN)
				{
					struct sockaddr_storage income_addr;
					socklen_t l = sizeof(struct sockaddr_storage);
					int income_fd = accept4(events[i].data.fd, (struct sockaddr *)&income_addr, &l, SOCK_NONBLOCK);
					if (income_fd == -1)
					{
						if (errno == EBADF || errno == ECONNABORTED || errno == EFAULT || errno == EINVAL || errno == ENOTSOCK || errno == EOPNOTSUPP || errno == EPROTO)
							error(-3, errno, "Unable to accept from socket %d, errno %d.", events[i].data.fd, errno);
						else
						{
							fprintf(stderr, "Unable to accept from socket %d, errno %d.", events[i].data.fd, errno);
							continue;
						}
					}
					// to handle epoll
					income_t *income = gen_income(income_fd, &income_addr);
					income->belong_epfd = thread->handle_epfd;
					struct epoll_event hevent;
					hevent.events = EPOLLOUT | EPOLLRDHUP | EPOLLET; // | EPOLLEXCLUSIVE;
					hevent.data.ptr = income;
					if (epoll_ctl(thread->handle_epfd, EPOLL_CTL_ADD, income_fd, &hevent) == -1)
					{
						fprintf(stderr, "Failed when add epoll entry\n");
						close_socket(income_fd);
						continue;
					}

					// TODO add to linked-list
				}
				else
				{
					fprintf(stderr, "Unable to wait listen socket epoll, epfd: %d, fd: %d!\n", thread->socket_epfd, events[i].data.fd);
					close_socket(events[i].data.fd);
					epoll_ctl(thread->socket_epfd, EPOLL_CTL_DEL, events[i].data.fd, events + i);

					return NULL;
				}
			}
		}
	}

	return NULL;
}

int start_all_threads(short v4_sfd, short v6_sfd)
{
	int socket_epfd = epoll_create1(0);
	struct epoll_event v4_se, v6_se;
	v4_se.events = EPOLLIN | EPOLLRDHUP | EPOLLET; // | EPOLLEXCLUSIVE;
	v4_se.data.fd = v4_sfd;
	epoll_ctl(socket_epfd, EPOLL_CTL_ADD, v4_sfd, &v4_se);
	v6_se.events = EPOLLIN | EPOLLRDHUP | EPOLLET; // | EPOLLEXCLUSIVE;
	v6_se.data.fd = v6_sfd;
	epoll_ctl(socket_epfd, EPOLL_CTL_ADD, v6_sfd, &v6_se);

	int handle_epfd = epoll_create1(0);

	accept_thread_t accept_threads[g_accept_threads_num];
	init_accept_threads(g_accept_threads_num, accept_threads, (void *(*)(void *))(&accept_start), socket_epfd, handle_epfd);

	handle_thread_t handle_threads[g_handle_threads_num];
	init_handle_threads(g_handle_threads_num, handle_threads, (void *(*)(void *))(&handle_start), handle_epfd);

	// output threads

	// watch dog threads

	// pthread_join()
	pthread_join(accept_threads->thread, NULL);
	for (int i = 1; i < g_accept_threads_num; i++)
		pthread_join(accept_threads[i].thread, NULL);
	pthread_join(handle_threads->thread, NULL);
	for (int i = 1; i < g_handle_threads_num; i++)
		pthread_join(handle_threads[i].thread, NULL);
	// join output threads
	// join watch dog threads

	return 0;
}
